package com.htfyun.roobofactory.rooboUtils;

import android.content.Context;
import android.text.TextUtils;
import android.util.Log;

import java.io.File;


/**
 * 加载 native so 文件的工具类
 */
public class NativeLoader {

    public static final boolean bDebug = false;
    private static final String TAG = "NativeLoader";
    private static final String JNI_EXT = ".so";
    private static final String JNI_DIR = "lib";

    /**
     * 计算 /data/data/<packageName>/lib/<libname>.so 的全路径
     *
     * @param c       Context.
     * @param libname 要加载的 so 库的名字，例如要加载 libsc.so 那么传递本参数为 sc.
     * @return so 库的全路径。
     */
    public static String getLibraryPath(Context c, String libname) {
        File filesDir = c.getFilesDir();
        if (filesDir != null) {
            File parentDir = filesDir.getParentFile();
            if (parentDir != null) {
                File jniFile = new File(parentDir.getAbsolutePath() + "/lib/lib" + libname + JNI_EXT);
                if (jniFile.exists()) {
                    return jniFile.getAbsolutePath();
                }
            }
        }

        return null;
    }

    /**
     * 尝试从 apk 中解压缩 so 出来
     *
     * @param c       Context.
     * @param libname 要加载的 so 库的名字，例如要加载 libsc.so 那么传递本参数为 sc.
     * @return 如果加载成功，返回 true，否则返回 false.
     */
    public static boolean loadFromApk(Context c, String libname, String lockName) {
        String libFilename = "lib" + libname + JNI_EXT;
        File libFile = c.getFileStreamPath(libFilename);
        String extractLibPath = libFile.getAbsolutePath();
        if (libFile.isFile()) {
            return loadLibraryPath(extractLibPath);
        } else {
            // UI进程和后台进程都可能释放so，需要进程锁
            SocketLock lock = new SocketLock(lockName);
            if (!lock.timedLock(10 * 1000)) {
                Log.e(TAG, "failed to lock: can't wait native manager ready");
                // 早死早超生
                throw new IllegalStateException(
                        "NativeManager: get lock failed");
            }
            String codePath = c.getPackageCodePath();
            if (ZipUtil.extract(codePath, "lib/armeabi/" + libFilename, libFile)) {
                try {
//                    FileUtils.setPermissions(extractLibPath, 0755, -1, -1);
                } catch (Throwable e) {
                    // if (CommonEnv.bDebug) {
                    // android.util.Log.e(TAG, "setPermission: " + e.getMessage());
                    // }
                }
                lock.release();
                return loadLibraryPath(extractLibPath);
            } else {
                lock.release();
                // if (CommonEnv.bDebug) {
                // android.util.Log.e(TAG, "Unzip " + codePath + " to " + extractLibPath +
                // " failed.");
                // }
                return false;
            }
        }

    }

    public static boolean defaultLoadLibrary(String libname) {
//        if (CommonEnv.bDebug) {
//            android.util.Log.e(TAG, "Loading " + libname);
//        }
        try {
            System.loadLibrary(libname);
            return true;
        } catch (Error e) {
//            if (CommonEnv.bDebug) {
//                android.util.Log.e(TAG, "loadLibrary: " + e.getMessage());
//            }
        }

        return false;
    }

    public static boolean loadLibraryPath(String path) {
//        if (CommonEnv.bDebug) {
        Log.i(TAG, "Loading " + path);
//        }
        try {
            Runtime.getRuntime().load(path);
            return true;
        } catch (Error e) {
//            if (CommonEnv.bDebug) {
            Log.e(TAG, "loadPath: " + e);
//            }
        }

        return false;
    }

    /**
     * 加载流程如下： 1.枚举data/data/com/lib目录下是否有该so，有并且加载成功则返回
     * 2.直接使用system.loadlibrary加载so name，加载成功则返回
     * 3.尝试新建files/lib目录，并修改权限为0755，失败则返回 4.如果files/lib内有libname，则尝试加载，成功则返回
     * 4.清理files/lib目录里所有满足前缀 prefixLibname的so 5.从apk包内重新释放lib/armeabi/libname
     * 文件到files/lib目录，修改权限为755，失败则返回 6.最后一次尝试加载重新释放好的文件，并返回加载结果
     *
     * @param c             Context
     * @param libname       需要加载的so name, 不需要带前缀“lib”和后缀".so"
     * @param prefixLibname 需要进行清理的so prefix，同样不需要带前缀"lib"
     * @return 加载是否成功
     */
    public static boolean load(Context c, String libname, String prefixLibname) {
        String path = getLibraryPath(c, libname);
        if (TextUtils.isEmpty(path) || !loadLibraryPath(path)) {
            // data/data/com/lib/xxx.so 不存在或者加载失败
            if (defaultLoadLibrary(libname)) {
                return true;
            }

            // 流程走到这里，说明 /data/<...>/lib 和 system.loadlib 的加载都失败了，那么尝试从 apk 中解压缩
            // so 出来
            // 默认解压到files/lib/ 目录下
            String libFilename = "lib" + libname + JNI_EXT;
            File filesDir = c.getFilesDir();
            String extractLibDir = filesDir.getAbsolutePath() + "/" + JNI_DIR;
            File dir = new File(extractLibDir);
            if (!dir.exists()) {
                if (!dir.mkdir()) {
                    return false;
                }

                /*int err = FileUtils.setPermissions(extractLibDir, 0755, -1, -1);
                if (err != 0){
                }*/
            }

            String extractLibPath = extractLibDir + "/" + libFilename;
            File libFile = new File(extractLibPath);
            if (libFile.isFile()) {
                if (loadLibraryPath(extractLibPath)) {
                    return true;
                }
            } else {
            }

            // 走到这里就得重新释放了，即使之前有，之前的也有问题，删了重新来
            // if (CommonEnv.bDebug) {
            Log.i(TAG, "Load failed, extract " + libFilename);
//            }

            // 先删一把
            cleanOldFiles(extractLibDir, "lib" + prefixLibname);
            // 释放
            String codePath = c.getPackageCodePath();
            if (ZipUtil.extract(codePath, "lib/armeabi-v7a/" + libFilename, libFile)) {
                return loadLibraryPath(extractLibPath);
            } else if (ZipUtil.extract(codePath, "lib/armeabi/" + libFilename, libFile)) {
                return loadLibraryPath(extractLibPath);
            } else {
                Log.e(TAG, "Unzip " + codePath + " to " + extractLibPath + " failed.");
                return false;
            }
        } else {
            return true;
        }
    }

    // delete old version file
    private static void cleanOldFiles(String path, String prefixToDel) {
        File[] allFiles = new File(path).listFiles();
        for (File file : allFiles) {
            if (file.isFile() && file.exists()) {
                String name = file.getName();
                if (name.startsWith(prefixToDel)) {
                    boolean rc = file.delete();
                    if (!rc) {
                        Log.e(TAG, "cleanOldFiles " + file.getAbsolutePath() + " fails!");
                    }
                }
            }
        }
    }

}
